package com.yanyeori.framework.datasource.switcher;

import com.yanyeori.framework.datasource.config.DatabaseContextHolder;
import com.yanyeori.framework.datasource.config.DynamicDataSource;
import com.yanyeori.framework.datasource.configprop.DataSourceConfigProps;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 数据源主从存活切换器
 *
 * @author chenkuan 2021/8/19
 */
@Slf4j
public class SurviveSwitcher {

    private final SurviveEviction surviveEviction;
    private final DataSourceConfigProps config;

    private final Map<String, List<String>> EXEC_SWITCH_MAP = new HashMap<>();

    public SurviveSwitcher(SurviveEviction surviveEviction, DataSourceConfigProps config) {
        this.surviveEviction = surviveEviction;
        this.config = config;
    }

    /**
     * 数据源存活检测及主从切换
     */
    public void switchDB(String dbKey, List<String> nowErrorList) {
        if (config.getConfig().getEvictionAndSwitchDbs() == null || !config.getConfig().getEvictionAndSwitchDbs().contains(dbKey)) {
            return;
        }
        if (nowErrorList == null) {
            nowErrorList = new ArrayList<>();
        }
        //校验当前数据源是否在主从库列表中
        Map<String, List<String>> primarySlaveDbMap = DynamicDataSource.getInstance().getPrimarySlaveDbMap();
        boolean containsPrimary = primarySlaveDbMap.containsKey(dbKey) && primarySlaveDbMap.get(dbKey).size() > 0;
        boolean containsSlave = false;
        String slaveMaster = null;
        List<String> slaveList = new ArrayList<>();
        if (!containsPrimary) {
            for (Map.Entry<String, List<String>> entry : primarySlaveDbMap.entrySet()) {
                List<String> list = entry.getValue();
                if (list.contains(dbKey)) {
                    containsSlave = true;
                    slaveMaster = entry.getKey();
                    for (String db : list) {
                        if (!nowErrorList.contains(db) && !dbKey.equals(db)) {
                            slaveList.add(db);
                        }
                    }
                    break;
                }
            }
        }
        if (!(containsPrimary || containsSlave)) {
            return;
        }
        //当前数据源有其他备份数据源，进行存活检测
        SurviveStatus surviveStatus = surviveEviction.survive(dbKey);
        if (surviveStatus.isSurvive()) {
            DatabaseContextHolder.setDBKey(dbKey);
            return;
        }
        //当前数据源异常
        nowErrorList.add(dbKey);
        if (containsPrimary) {
            //主库异常
            int dbErrorTimesSwitch = config.getConfig().getDbErrorTimesSwitch();
            if (dbErrorTimesSwitch == 0 || surviveStatus.getErrorTimes() < dbErrorTimesSwitch) {
                //选择第一个从库执行
                List<String> list = primarySlaveDbMap.get(dbKey);
                for (String slave : list) {
                    if (nowErrorList.contains(slave)) {
                        continue;
                    }
                    switchDB(slave, nowErrorList);
                    return;
                }
                log.error("Primary datasource [{}] failed, and no secondary datasource is available for this primary datasource", dbKey);
            } else {
                //异常次数超限，则剔除主库，并将第一个从库转变为主库
                List<String> slaves = primarySlaveDbMap.get(dbKey);
                for (String slave : slaves) {
                    if (nowErrorList.contains(slave)) {
                        continue;
                    }
                    //从库转变为主库
                    List<String> newSlaves = slaves.stream().filter(x -> !x.equals(slave)).collect(Collectors.toList());
                    primarySlaveDbMap.put(slave, newSlaves);
                    log.warn("Datasource [{}] replace [{}] as the new primary datasource", slave, dbKey);
                    primarySlaveDbMap.remove(dbKey);
                    log.warn("Datasource [{}] has had {} times exceptions and is being removed from datasource list", dbKey, surviveStatus.getErrorTimes());
                    //切换历史列表维护
                    List<String> oldSwitchList = EXEC_SWITCH_MAP.get(dbKey);
                    List<String> switchList = new LinkedList<>();
                    if (oldSwitchList != null) {
                        switchList.addAll(oldSwitchList);
                        EXEC_SWITCH_MAP.remove(dbKey);
                    }
                    switchList.add(dbKey);
                    EXEC_SWITCH_MAP.put(slave, switchList);
                    switchDB(slave, nowErrorList);
                    return;
                }
            }
        } else {
            //从库异常
            int dbErrorTimesSwitch = config.getConfig().getDbErrorTimesSwitch();
            if (dbErrorTimesSwitch > 0 && surviveStatus.getErrorTimes() >= dbErrorTimesSwitch) {
                List<String> slaves = primarySlaveDbMap.get(slaveMaster);
                slaves.remove(dbKey);
                log.warn("Datasource [{}] has had {} times exceptions and is being removed from datasource list", dbKey, surviveStatus.getErrorTimes());
                //切换历史列表维护
                EXEC_SWITCH_MAP.computeIfAbsent(slaveMaster, k -> new LinkedList<>()).add(dbKey);
            }
            if (slaveList.size() > 0) {
                //首先选择其他从库
                switchDB(slaveList.get(0), nowErrorList);
            } else {
                //无从库，选择主库
                if (nowErrorList.contains(slaveMaster)) {
                    log.error("Slave datasource [{}] failed, and no slave or master datasource is available for this slave datasource", dbKey);
                    return;
                }
                switchDB(slaveMaster, nowErrorList);
            }
        }
    }
}
